Binder的权限控制
在AOSP中存在大量的如下代码:
1 | final long origId = Binder.clearCallingIdentity(); |
这段代码到底有什么作用呢?它们总是成对的出现,如影随行,今天我们就来探究下里面的玄机。先透露下,实际上这段代码就是Binder的权限控制机制。
1 | //frameworks/base/core/java/android/os/Binder.java |
这两个方法都是在native层实现的,不过我们可以从注释中看出一些大概的东西:
- clearCallingIdentity会在当前线程中重置到来的IPC标识,在将要处理调用时,调用者可能会需要调用本进程其他对象的接口。这些对象都需要再调用上做权限检查(因此它们检查当前进程的权限,而不是其他发起IPC调用的进程)。
- restoreCallingIdentity会在当前线程恢复先前被clearCallingIdentity清理的IPC标记。
看了上面的注释可能还不是很明白,其他说白了就是,大概意思就是:
如果A进程和B进程进行IPC调用,当A的binder线程通过IPC调用到B的binder线程中时(B线程会保存A的IPC标记,实际就是IPCThread中的mCallingPid和mCallingUid),然后如果此时B的这个binder线程要使用Binder访问本线程中的其他组件或者对象,如果这些组件或者对象需要检查访问者的IPC标记(即pid和uid),那么此时需要将B线程保存的Binder IPC标记修改为自身进程的IPC标记,这样组件或对象针对的是B进行权限检查(因为是在B中通过Binder调用它,而不是A)。这时候就需要通过clearCallingIdentity将Binder线程的IPC标记设置为当前进程的pid和uid,并将调用者A的pid和uid暂时保存起来,当B调用完后再通过restoreCallingIdentity恢复即可。
大概意思如果还不是特别明白,下面我们接着看看源码,说不定会对你有所启发。
1 | //frameworks/base/core/jni/android_util_Binder.cpp |
clearCallingIdentify和restoreCallingIdentify都依赖于IPCThreadState的实现。了解Binder的同学都知道,IPCThreadState负责和Binder驱动进行通信,它是线程单例的。
1 | class IPCThreadState |
IPCThreadState有两个成员mCallingPid和mCallingUid,它们代表着Binder调用的进程pid和uid。可以通过getCallingPid和getCallingUid获取到它们的值。那么这两个值是怎么赋值的呢?
1 | status_t IPCThreadState::executeCommand(int32_t cmd) |
在IPCThreadState::executeCommand中,Binder进程会从读取驱动中的内容(从BR_XXX可以看出来当前Binder线程负责读驱动),数据被读取到binder_transaction_data,这是Binder IPC中最常见的情况,大多数IPC调用都走BC_TRANSACTION和BR_TRANSACTION,这里我们可以看到取到的数据是被BBinder的transact拿去处理了,现在我们先不管读到的内容是如何被BBinder处理的,我们看到在调用前后,会将mCallingPid和mCalingUid暂时保存在origPid和origUid,然后将tr中的sender_pid和sender_euid分别赋值给mCallingPid和mCallingUid,sender_pid和sender_euid分别代表了发送该数据的进程pid和uid也可以理解为发起Binder请求的进程pid和uid,它们都是由Binder驱动负责填入的。在Binder被调用端执行完后,最后将其恢复到之前的调用进程的pid和uid。在BBinder执行期间,它可以通过Binder的getCallingPid和getCallingUid来取到调用者的pid和uid,也就是这里的mCallingPid和mCallingUid,以此完成可能需要的权限检查。
知道了mCallingPid和mCallingUid怎么来的,那么我们看看IPCThreadState的clearCallingIdentity和restoreCallingIdentity会做些什么呢?
1 | int64_t IPCThreadState::clearCallingIdentity() |
clearCallingIdentify实际上是将调用进程的uid保存在了int64_t的高32位中,低32位保存的是pid,这个值作为token返回。其中调用了clearCaller获取当前进程的pid和uid并保存在mCallingPid和mCallingUid中。而restoreCallingIdentity做了相反的操作从token中取到之前保存的mCallingUid和mCallingPid。
总结
Binder的权限控制并没有想象中那么复杂, 事实上,它只是为Binder Server提供了权限检查的凭证(即pid和uid),而不能说所有的权限工作都会由Binder负责处理,这也是不现实的,每个Binder的进程对于资源访问的权限控制策略都会不尽相同,这个是Binder没法去做控制的是不应该控制的,Binder能提供的仅仅是为权限检查的进程提供这个凭证,不过,从大的方面来讲,这也确实是一种权限控制策略。